package com.xgimi.open.skill.datamanager.biz.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.xgimi.gmui.glog.sdk.GlogRecorder;
import com.xgimi.open.skill.datamanager.biz.common.Enum.ExceptionErrorCode;
import com.xgimi.open.skill.datamanager.biz.common.exception.BizAssert;
import com.xgimi.open.skill.datamanager.biz.common.exception.BizException;
import com.xgimi.open.skill.datamanager.biz.common.redis.RedisLockService;
import com.xgimi.open.skill.datamanager.biz.constant.NluConstants;
import com.xgimi.open.skill.datamanager.biz.service.SkillVersionBizService;
import com.xgimi.open.skill.datamanager.biz.service.SpeechRuleBizService;
import com.xgimi.open.skill.datamanager.biz.utils.BusinessIdUtil;
import com.xgimi.open.skill.datamanager.biz.utils.RedisKeyBuilder;
import com.xgimi.open.skill.datamanager.biz.utils.RegularPatternUtil;
import com.xgimi.open.skill.datamanager.domain.entity.*;
import com.xgimi.open.skill.datamanager.domain.service.IntentService;
import com.xgimi.open.skill.datamanager.domain.service.IntentSlotService;
import com.xgimi.open.skill.datamanager.domain.service.SlotService;
import com.xgimi.open.skill.datamanager.domain.service.SpeechRuleService;
import com.xgimi.open.skill.datamanager.service.request.BatchSpeechRuleReqDTO;
import com.xgimi.open.skill.datamanager.service.request.SpeechRuleReqDTO;
import com.xgimi.open.skill.datamanager.service.response.BatchSpeechRuleRespDTO;
import com.xgimi.open.skill.datamanager.service.response.SpeechRuleRespDTO;
import com.xgimi.open.skill.datamanager.service.response.page.IPageUtils;
import com.xgimi.open.skill.datamanager.service.response.page.PageResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author coderzpw.zhang
 * @version 1.0
 * @description 描述
 * @date 2023/2/8 14:32
 */
@Slf4j
@Service
public class SpeechRuleBizServiceImpl implements SpeechRuleBizService {
    public static final String EMPTY_STR = "";
    private final IntentService intentService;
    private final SpeechRuleService speechRuleService;
    private final RedisLockService redisLockService;
    private final SkillVersionBizService skillVersionBizService;
    private final IntentSlotService intentSlotService;
    private final SlotService slotService;

    private final GlogRecorder glogRecorder;

    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    public SpeechRuleBizServiceImpl(IntentService intentService,
                                    SpeechRuleService speechRuleService,
                                    RedisLockService redisLockService,
                                    SkillVersionBizService skillVersionBizService,
                                    IntentSlotService intentSlotService,
                                    SlotService slotService,
                                    GlogRecorder glogRecorder) {
        this.intentService = intentService;
        this.speechRuleService = speechRuleService;
        this.redisLockService = redisLockService;
        this.skillVersionBizService = skillVersionBizService;
        this.intentSlotService = intentSlotService;
        this.slotService = slotService;
        this.glogRecorder = glogRecorder;
    }

    @Override
    public Boolean addSpeechRule(SpeechRuleReqDTO speechRuleReqDTO) {
        SpeechRuleDO speechRuleDO = new SpeechRuleDO();
        BeanUtils.copyProperties(speechRuleReqDTO, speechRuleDO);
        speechRuleDO.setCreatedBy(speechRuleReqDTO.getCreatedBy());
        // 判断意图是否存在
        IntentDO intentDO = intentService.queryById(speechRuleDO.getIntentId());
        BizAssert.notNull(intentDO, ExceptionErrorCode.INTENT_NOT_EXISTS);
        SkillVersionDO skillVersionDO = skillVersionBizService.checkSkillVersion(intentDO.getSkillVid());
        boolean ret;
        String skillLockKey = skillLock(skillVersionDO.getBusinessId());
        try {
            // 获取当前意图引用的所有槽位
            List<IntentSlotDO> intentSlotDOS = intentSlotService.listByIntentId(speechRuleDO.getIntentId());
            List<Long> slotIds = intentSlotDOS.stream().map(IntentSlotDO::getSlotId).collect(Collectors.toList());
            List<SlotDO> slotDOS = new ArrayList<>();
            if (!CollectionUtils.isEmpty(slotIds)) {
                slotDOS = slotService.listByIds(slotIds);
            }
            // 校验说法规则
            checkRule(speechRuleReqDTO.getRuleText(), slotDOS);
            Boolean isConform = RegularPatternUtil.checkSpeechRule(speechRuleReqDTO);
            BizAssert.assertTrue(isConform, ExceptionErrorCode.SPEECH_RULE_NOT_CONFORM);
            // 新增说法
            speechRuleDO.setBusinessId(BusinessIdUtil.getSpeechRuleUUID());
            ret = speechRuleService.save(speechRuleDO);
        } finally {
            redisLockService.unlock(skillLockKey);
        }
        glogRecorder.recordAdd(speechRuleReqDTO.getCreatedBy(),
                "speechRule", "addSpeechRule",
                String.valueOf(ret), speechRuleReqDTO);
        return ret;
    }

    @Override
    public List<SpeechRuleRespDTO> listByIntentId(Long intentId) {
        List<SpeechRuleDO> list = speechRuleService.listByIntentId(intentId);
        List<SpeechRuleRespDTO> result = list.stream().map(x -> {
            SpeechRuleRespDTO DTO = new SpeechRuleRespDTO();
            BeanUtils.copyProperties(x, DTO);
            return DTO;
        }).collect(Collectors.toList());
        return result;
    }

    @Override
    public BatchSpeechRuleRespDTO batchCreate(BatchSpeechRuleReqDTO batchSpeechRuleReqDTO) {
        BatchSpeechRuleRespDTO ret = new BatchSpeechRuleRespDTO();
        // 判断意图是否存在
        IntentDO intentDO = intentService.queryById(batchSpeechRuleReqDTO.getIntentId());
        BizAssert.notNull(intentDO, ExceptionErrorCode.INTENT_NOT_EXISTS);
        SkillVersionDO skillVersionDO = skillVersionBizService.checkSkillVersion(intentDO.getSkillVid());
        boolean flag1;
        String skillLockKey = skillLock(skillVersionDO.getBusinessId());
        try {
            Long intentId = batchSpeechRuleReqDTO.getIntentId();
            Integer isStrong = batchSpeechRuleReqDTO.getStrong();
            List<String> ruleTexts = Arrays.asList(batchSpeechRuleReqDTO.getRuleTexts());
            List<IntentSlotDO> intentSlotDOS = intentSlotService.listByIntentId(intentId);
            List<Long> slotIds = intentSlotDOS.stream().map(IntentSlotDO::getSlotId).collect(Collectors.toList());
            List<SlotDO> slotDOS = slotService.listByIds(slotIds);
            // 校验批量说法规则
            List<Boolean> verifyList = batchCheck(ruleTexts, slotDOS);
            ret.setVerifyList(verifyList);
            Boolean flag = verifyList.stream().allMatch(Boolean::booleanValue);
            if (Boolean.FALSE.equals(flag)) {
                ret.setFlag(flag);
                return ret;
            }
            List<SpeechRuleDO> ruleDoList = ruleTexts.stream().map(x -> {
                SpeechRuleDO speechRuleDO = new SpeechRuleDO();
                speechRuleDO.setCreatedBy(batchSpeechRuleReqDTO.getCreatedBy());
                speechRuleDO.setIntentId(intentId);
                speechRuleDO.setRuleText(x);
                speechRuleDO.setStrong(isStrong);
                speechRuleDO.setBusinessId(BusinessIdUtil.getSpeechRuleUUID());
                return speechRuleDO;
            }).collect(Collectors.toList());
            flag1 = speechRuleService.saveBatch(ruleDoList);
        } finally {
            redisLockService.unlock(skillLockKey);
        }
        ret.setFlag(flag1);
        glogRecorder.recordAdd(batchSpeechRuleReqDTO.getUpdatedBy(),
                "speechRule", "batchCreate",
                String.valueOf(ret), batchSpeechRuleReqDTO);
        return ret;
    }

    @Override
    public List<SpeechRuleRespDTO> listByIntentIds(List<Long> intentIds) {
        List<SpeechRuleDO> speechRuleDOS = speechRuleService.queryByIntentIds(intentIds);
        List<SpeechRuleRespDTO> ret = speechRuleDOS.stream().map(x -> {
            SpeechRuleRespDTO speechRuleRespDTO = new SpeechRuleRespDTO();
            BeanUtils.copyProperties(x, speechRuleRespDTO);
            return speechRuleRespDTO;
        }).collect(Collectors.toList());
        return ret;
    }


    private List<Boolean> batchCheck(List<String> ruleTexts, List<SlotDO> slotDOS) {
        List<Boolean> ret = new ArrayList<>();
        for (String text : ruleTexts) {
            Boolean isConform = checkRule(text, slotDOS);
            ret.add(isConform);
        }
        return ret;
    }

    private Boolean checkRule(String text, List<SlotDO> slotDOS) {
        // 1、校验正则
        Boolean regularFlag = RegularPatternUtil.checkPattern(text);
        if (!regularFlag) {
            return Boolean.FALSE;
        }
        // 2、校验槽位是否符合
        Set<String> slotNames = slotDOS.stream().map(SlotDO::getSlotName).collect(Collectors.toSet());
        Set<String> speechSlotNames = pickUpSlots(text);
        boolean isAllContains = speechSlotNames.stream().allMatch(x -> slotNames.contains(x));
        if (!isAllContains) {
            throw new BizException(ExceptionErrorCode.INTENT_NOT_REFERENCE_SLOT);
        }
        return Boolean.TRUE;
    }

    /**
     * 抽取该说法中所有的槽位名称，例如：【aa】xx【bb】 -> ["aa", "bb"]
     *
     * @param text
     * @return
     */
    public Set<String> pickUpSlots(String text) {
        text = text.replace(" ", "").replace("\t", "");
        if (EMPTY_STR.equals(text)) {
            return Sets.newHashSet();
        }
        Set<String> slotNames = new HashSet<>();
        Boolean inSlotName = Boolean.FALSE;
        String tempName = "";
        for (int i = 0; i < text.length(); i++) {
            String currentChar = text.charAt(i) + "";
            if (RegularPatternUtil.SLOT_LEFT.equals(currentChar)) {
                inSlotName = Boolean.TRUE;
                continue;
            }
            if (RegularPatternUtil.SLOT_RIGHT.equals(currentChar)) {
                inSlotName = Boolean.FALSE;
                slotNames.add(tempName);
                tempName = "";
                continue;
            }
            if (inSlotName) {
                tempName += currentChar;
            }
        }
        return slotNames;
    }


    @Override
    public Boolean deleteSpeechRule(SpeechRuleReqDTO speechRuleReqDTO) {
        BizAssert.notNull(speechRuleReqDTO.getId(), ExceptionErrorCode.SPEECH_RULE_ID_IS_NULL);
        // 先根据技能id查询 是否存在该说法规则
        SpeechRuleDO res = speechRuleService.queryById(speechRuleReqDTO.getId());
        BizAssert.notNull(res, ExceptionErrorCode.SPEECH_RULE_NOT_EXISTS);
        IntentDO intentDO = intentService.queryById(res.getIntentId());
        SkillVersionDO skillVersionDO = skillVersionBizService.checkSkillVersion(intentDO.getSkillVid());
        boolean ret;
        String skillLockKey = skillLock(skillVersionDO.getBusinessId());
        try {
            SpeechRuleDO speechRuleDO = new SpeechRuleDO();
            BeanUtils.copyProperties(speechRuleReqDTO, speechRuleDO);
            // 删除技能
            ret = speechRuleService.removeById(speechRuleDO.getId());
            // 处理 槽位-意图 关联关系变动逻辑
            // 1、调用pickUpSlots，先拿到当前说法的所有槽位， 对于少的槽位则 判断槽位引用关系是否发送改变
            // 先拿到当前说法引用的槽位名称
            Set<String> slotNames = pickUpSlots(res.getRuleText());
            handleSlotIntent(slotNames, res.getIntentId(), skillVersionDO.getId());
        } catch (Exception e) {
            log.warn("说法删除失败！{}", JSON.toJSONString(speechRuleReqDTO));
            log.error(e.getMessage());
            ret = Boolean.FALSE;
        } finally {
            redisLockService.unlock(skillLockKey);
        }
        glogRecorder.recordDel(speechRuleReqDTO.getUpdatedBy(),
                "speechRule", "deleteSpeechRule",
                speechRuleReqDTO.toString());
        return ret;
    }

    /**
     * @param slotNames：减少的槽位名称
     * @param intentId：意图id
     * @param skillVid：技能版本id
     */
    private void handleSlotIntent(Set<String> slotNames, Long intentId, Long skillVid) {
        // 获取当前意图所有的说法规则
        List<SpeechRuleDO> speechRuleDOS = speechRuleService.listByIntentId(intentId);
        List<String> allSpeechRules = speechRuleDOS.stream().map(SpeechRuleDO::getRuleText).collect(Collectors.toList());
        // 保存【意图-槽位】关联取消的槽位
        List<String> removedSlot = new ArrayList<>();
        for (String slotName : slotNames) {
            // 所有说法规则 都不包括 此槽位，则将词槽位添加到removedSlot
            boolean isAllRuleNotContains = allSpeechRules.stream().
                    noneMatch(rule -> {
                        Set<String> slotSet = pickUpSlots(rule);
                        return slotSet.contains(slotName);
                    });
            if (isAllRuleNotContains) {
                removedSlot.add(slotName);
            }
        }
        if (CollectionUtils.isEmpty(removedSlot)) {
            return;
        }
        // 删除关联关系
        List<SlotDO> slotDOS = slotService.listBySlotNames(removedSlot, skillVid);
        // 2、遍历当前意图的所有说法规则，若都不引用该槽位，则删掉 意图-槽位 的关联关系
        if (!CollectionUtils.isEmpty(slotDOS)) {
            // 删除掉这些 intent-slot 的关联关系
            List<IntentSlotDO> intentSlotDOS = intentSlotService.listByIntentId(intentId);
            Set<Long> slotIds = slotDOS.stream().map(SlotDO::getId).collect(Collectors.toSet());
            List<Long> intentSlotIds = intentSlotDOS.stream().filter(x -> slotIds.contains(x.getSlotId())).map(IntentSlotDO::getId).collect(Collectors.toList());
            boolean ret = intentSlotService.removeByIds(intentSlotIds);
        }
    }

    @Override
    public Boolean updateSpeechRule(SpeechRuleReqDTO speechRuleReqDTO) {
        BizAssert.notNull(speechRuleReqDTO.getId(), ExceptionErrorCode.SPEECH_RULE_ID_IS_NULL);
        // 先根据槽位id查询 是否存在该槽位
        SpeechRuleDO from = speechRuleService.queryById(speechRuleReqDTO.getId());
        BizAssert.notNull(from, ExceptionErrorCode.SPEECH_RULE_NOT_EXISTS);
        IntentDO intentDO = intentService.queryById(from.getIntentId());
        SkillVersionDO skillVersionDO = skillVersionBizService.checkSkillVersion(intentDO.getSkillVid());
        boolean ret;
        String skillLockKey = skillLock(skillVersionDO.getBusinessId());
        try {
            // 获取当前意图引用的所有槽位
            List<IntentSlotDO> intentSlotDOS = intentSlotService.listByIntentId(intentDO.getId());
            List<Long> slotIds = intentSlotDOS.stream().map(IntentSlotDO::getSlotId).collect(Collectors.toList());
            List<SlotDO> slotDOS = new ArrayList<>();
            if (!CollectionUtils.isEmpty(slotIds)) {
                slotDOS = slotService.listByIds(slotIds);
            }
            // 校验说法规则
            Boolean isConform = checkRule(speechRuleReqDTO.getRuleText(), slotDOS);
            BizAssert.assertTrue(isConform, ExceptionErrorCode.SPEECH_RULE_NOT_CONFORM);
            SpeechRuleDO speechRuleDO = new SpeechRuleDO();
            BeanUtils.copyProperties(speechRuleReqDTO, speechRuleDO);
            speechRuleDO.setUpdatedBy(speechRuleDO.getUpdatedBy());
            // 修改技能
            ret = speechRuleService.updateById(speechRuleDO);
            // 处理 意图-槽位 关联关系
            String oldRuleText = from.getRuleText();
            String newRuleText = speechRuleReqDTO.getRuleText();
            handleReduceIntentSlot(oldRuleText, newRuleText, intentDO.getId(), intentDO.getSkillVid());
        } catch (Exception e) {
            log.warn("说法修改失败：{}", JSON.toJSONString(speechRuleReqDTO));
            ret = Boolean.FALSE;
        } finally {
            redisLockService.unlock(skillLockKey);
        }
        SpeechRuleDO to = speechRuleService.queryById(speechRuleReqDTO.getId());
        glogRecorder.recordUpdate(speechRuleReqDTO.getUpdatedBy(),
                "speechRule", "updateSpeechRule",
                String.valueOf(ret), from, to);
        return ret;
    }

    private void handleReduceIntentSlot(String oldRuleText, String newRuleText, Long intentId, Long skillVid) {
        Set<String> oldSlotNames = pickUpSlots(oldRuleText);
        Set<String> newSlotNames = pickUpSlots(newRuleText);
        Set<String> reduceSlotNames = oldSlotNames.stream().filter(x -> !newSlotNames.contains(x)).collect(Collectors.toSet());
        handleSlotIntent(reduceSlotNames, intentId, skillVid);
    }

    @Override
    public PageResult<SpeechRuleRespDTO> pageQuery(SpeechRuleReqDTO speechRuleReqDTO) {
        SpeechRuleDO speechRuleDO = new SpeechRuleDO();
        BeanUtils.copyProperties(speechRuleReqDTO, speechRuleDO);
        // 构造分页查询的入参
        IPage<SpeechRuleDO> page = new Page<>(speechRuleReqDTO.getCurrentPage(), speechRuleReqDTO.getPageSize());
        // 调用domain层分页查询的方法
        IPage<SpeechRuleDO> iPage = speechRuleService.pageQuery(page, speechRuleDO);
        // 若查询没结果，则返回空pageResult
        if (CollectionUtils.isEmpty(iPage.getRecords())) {
            return new PageResult<>(Lists.newArrayList(), IPageUtils.noDataPaginator(speechRuleReqDTO.getPageSize()));
        }
        // 若有结果，则将 DO 转成 DTO
        List<SpeechRuleRespDTO> list = iPage.getRecords().stream().map(d -> {
            SpeechRuleRespDTO dto = new SpeechRuleRespDTO();
            BeanUtils.copyProperties(d, dto);
            return dto;
        }).collect(Collectors.toList());
        return new PageResult<>(list, IPageUtils.transformToPaginator(iPage));
    }

    /**
     * 加技能锁
     *
     * @param businessId
     */
    private String skillLock(String businessId) {
        String skillUpdateLockKey = RedisKeyBuilder.buildSkillUpdateLockKey(businessId);
        boolean locked = redisLockService.lock(skillUpdateLockKey, NluConstants.NLU_LOCK_EXPIRE_TTS,
                NluConstants.NLU_LOCK_WAIT_TIME_MILLS);
        if (!locked) {
            log.warn("获取技能写锁失败 SkillAppId:{}", businessId);
            throw new BizException(ExceptionErrorCode.SKILL_UPDATE_TRAIN);
        }
        return skillUpdateLockKey;
    }


}
